package com.xuanyuanxinyu.core.bus;

import android.os.Handler;
import android.os.Looper;

import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.ExternalLiveData;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.Observer;

import com.xuanyuanxinyu.utils.log.AppLog;

import java.util.HashMap;
import java.util.Map;

/**
 * CM
 *
 * @author ken.luo
 */
public final class AppLiveBus {

    private static final String TAG = "LiveEventBus";

    private final Map<String, LiveEvent<Object>> mBus;

    public static AppLiveBus get() {
        return SingletonHolder.DEFAULT_BUS;
    }

    private static class SingletonHolder {
        private static final AppLiveBus DEFAULT_BUS = new AppLiveBus();
    }

    private AppLiveBus() {
        mBus = new HashMap<>();
    }

    public synchronized <T> Observable<T> with(String key, Class<T> type) {
        if (!mBus.containsKey(key)) {
            mBus.put(key, new LiveEvent<>(key));
        }
        return (Observable<T>) mBus.get(key);
    }

    private static class LiveEvent<T> implements Observable<T> {

        @NonNull
        private final String mKey;
        private final LifecycleLiveData<T> mLiveData;
        private final Map<Observer, ObserverWrapper<T>> mObserverMap = new HashMap<>();
        private final Handler mainHandler = new Handler(Looper.getMainLooper());

        LiveEvent(@NonNull String key) {
            this.mKey = key;
            this.mLiveData = new LifecycleLiveData<>(key);
        }

        private boolean isMainThread() {
            return (Looper.getMainLooper() == Looper.myLooper());
        }

        @Override
        public void post(T value) {
            if (isMainThread()) {
                postInternal(value);
            } else {
                mainHandler.post(new PostValueTask(value));
            }
        }

        @Override
        public void postDelay(T value, long delay) {
            mainHandler.postDelayed(new PostValueTask(value), delay);
        }

        @Override
        public void postDelay(LifecycleOwner owner, final T value, long delay) {
            mainHandler.postDelayed(new PostLifeValueTask(value, owner), delay);
        }

        @Override
        public void postOrderly(T value) {
            mainHandler.post(new PostValueTask(value));
        }

        @Override
        public void observe(@NonNull final LifecycleOwner owner, @NonNull final Observer<T> observer) {
            if (isMainThread()) {
                observeInternal(owner, observer);
            } else {
                mainHandler.post(() -> observeInternal(owner, observer));
            }
        }

        @Override
        public void observeSticky(@NonNull final LifecycleOwner owner, @NonNull final Observer<T> observer) {
            if (isMainThread()) {
                observeStickyInternal(owner, observer);
            } else {
                mainHandler.post(() -> observeStickyInternal(owner, observer));
            }
        }

        @Override
        public void observeForever(@NonNull final Observer<T> observer) {
            if (isMainThread()) {
                observeForeverInternal(observer);
            } else {
                mainHandler.post(() -> observeForeverInternal(observer));
            }
        }

        @Override
        public void observeStickyForever(@NonNull final Observer<T> observer) {
            if (isMainThread()) {
                observeStickyForeverInternal(observer);
            } else {
                mainHandler.post(() -> observeStickyForeverInternal(observer));
            }
        }

        @Override
        public void removeObserver(@NonNull final Observer<T> observer) {
            if (isMainThread()) {
                removeObserverInternal(observer);
            } else {
                mainHandler.post(() -> removeObserverInternal(observer));
            }
        }

        @MainThread
        private void postInternal(T value) {
            mLiveData.setValue(value);
        }

        @MainThread
        private void observeInternal(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {
            ObserverWrapper<T> observerWrapper = new ObserverWrapper<>(observer);
            observerWrapper.preventNextEvent = mLiveData.getVersion() > ExternalLiveData.START_VERSION;
            mLiveData.observe(owner, observerWrapper);
        }

        @MainThread
        private void observeStickyInternal(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {
            ObserverWrapper<T> observerWrapper = new ObserverWrapper<>(observer);
            mLiveData.observe(owner, observerWrapper);
        }

        @MainThread
        private void observeForeverInternal(@NonNull Observer<T> observer) {
            ObserverWrapper<T> observerWrapper = new ObserverWrapper<>(observer);
            observerWrapper.preventNextEvent = mLiveData.getVersion() > ExternalLiveData.START_VERSION;
            mObserverMap.put(observer, observerWrapper);
            mLiveData.observeForever(observerWrapper);
        }

        @MainThread
        private void observeStickyForeverInternal(@NonNull Observer<T> observer) {
            ObserverWrapper<T> observerWrapper = new ObserverWrapper<>(observer);
            mObserverMap.put(observer, observerWrapper);
            mLiveData.observeForever(observerWrapper);
        }

        @MainThread
        private void removeObserverInternal(@NonNull Observer<T> observer) {
            Observer<T> realObserver;
            if (mObserverMap.containsKey(observer)) {
                realObserver = mObserverMap.remove(observer);
            } else {
                realObserver = observer;
            }
            mLiveData.removeObserver(realObserver);
        }

        private class LifecycleLiveData<T> extends ExternalLiveData<T> {

            private final String mKey;

            public LifecycleLiveData(String key) {
                this.mKey = key;
            }

            @Override
            protected Lifecycle.State observerActiveLevel() {
                return Lifecycle.State.CREATED;
            }

            @Override
            public void removeObserver(@NonNull Observer<? super T> observer) {
                super.removeObserver(observer);
                if (!mLiveData.hasObservers()) {
                    AppLiveBus.get().mBus.remove(mKey);
                }
                AppLog.INSTANCE.d(AppLiveBus.class, AppLog.GROUP_EVENT, " removeObserver() observer removed: " + observer);
            }
        }

        private class PostValueTask implements Runnable {
            private final Object mNewValue;

            public PostValueTask(@NonNull Object newValue) {
                this.mNewValue = newValue;
            }

            @Override
            public void run() {
                postInternal((T) mNewValue);
            }
        }

        private class PostLifeValueTask implements Runnable {
            private final Object mNewValue;
            private final LifecycleOwner mOwner;

            public PostLifeValueTask(@NonNull Object newValue, @Nullable LifecycleOwner owner) {
                this.mNewValue = newValue;
                this.mOwner = owner;
            }

            @Override
            public void run() {
                if (mOwner != null) {
                    if (mOwner.getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) {
                        postInternal((T) mNewValue);
                    }
                }
            }
        }
    }

    private static class ObserverWrapper<T> implements Observer<T> {

        @NonNull
        private final Observer<T> mObserver;
        private boolean preventNextEvent = false;

        ObserverWrapper(@NonNull Observer<T> observer) {
            this.mObserver = observer;
        }

        @Override
        public void onChanged(@Nullable T t) {
            if (preventNextEvent) {
                preventNextEvent = false;
                return;
            }
            try {
                mObserver.onChanged(t);
            } catch (ClassCastException e) {
                AppLog.INSTANCE.d(AppLiveBus.class, AppLog.GROUP_EVENT, " onChanged() class cast error on message received: " + t + e.getMessage());
            } catch (Exception e) {
                AppLog.INSTANCE.d(AppLiveBus.class, AppLog.GROUP_EVENT, " onChanged() error on message received: " + t + e.getMessage());
            }
        }
    }

}
